package Renderer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import LDraw.Support.MatrixMath;
import LDraw.Support.Range;
public class Mesh {
/**
* @uml.property name="vertex_count"
*/
int vertex_count; // Number of vertices so far.
/**
* @uml.property name="vertex_capacity"
*/
int vertex_capacity; // Number of vertices we have storage for in our vertex
// array.
/**
* @uml.property name="unique_vertex_count"
*/
int unique_vertex_count;// Number of actual unique vertices after merging
// for export.
/**
* @uml.property name="vertices"
* @uml.associationEnd multiplicity="(0 -1)"
*/
ArrayList<Vertex> vertices; // malloc'd array of vertices.
/**
* @uml.property name="face_count"
*/
int face_count; // Number of total faces so far.
/**
* @uml.property name="tri_count"
*/
int tri_count; // Number of triangle faces??
/**
* @uml.property name="quad_count"
*/
int quad_count; // Number of quad faces.
/**
* @uml.property name="poly_count"
*/
int poly_count; // Number of quad + triangle faces.
/**
* @uml.property name="line_count"
*/
int line_count; // Number of line faces. Lines must be AFTER quads and tris.
/**
* @uml.property name="face_capacity"
*/
int face_capacity; // Face capacity reserved in array.
/**
* @uml.property name="faces"
* @uml.associationEnd multiplicity="(0 -1)"
*/
Face[] faces; // Malloc'd face memory.
/**
* @uml.property name="rtree"
* @uml.associationEnd
*/
RTree rtree;// index; // Root node of r-tree that indexes vertices.
// todo
// #if DEBUG
// int flags; // For debugging, we can flag various conditions that aren't
// errors but are strange (due to LDraw precision issues).
// #endif
/**
* @uml.property name="highest_tid"
*/
int highest_tid; // Highest TID - we have this + 1 total textures in this
// mesh.
// Create a new mesh to smooth. You must pass in the _exact_ number of
// tris,
// quads and lines that you will later pass in.
public Mesh(int tri_count, int quad_count, int line_count) {
vertex_count = 0;
vertex_capacity = tri_count * 3 + quad_count * 4 + line_count * 2;
vertices = new ArrayList<Vertex>(vertex_capacity);
for (int i = 0; i < vertex_capacity; i++)
vertices.add(new Vertex());
face_count = 0;
face_capacity = tri_count + quad_count + line_count;
poly_count = tri_count + quad_count;
this.line_count = line_count;
this.tri_count = tri_count;
this.quad_count = quad_count;
faces = new Face[face_capacity];
for (int i = 0; i < face_capacity; i++) {
faces[i] = new Face();
}
highest_tid = 0;
}
/**
* @return
* @uml.property name="vertex_count"
*/
public int getVertex_count() {
return vertex_count;
}
/**
* @param vertex_count
* @uml.property name="vertex_count"
*/
public void setVertex_count(int vertex_count) {
this.vertex_count = vertex_count;
}
/**
* @return
* @uml.property name="vertex_capacity"
*/
public int getVertex_capacity() {
return vertex_capacity;
}
/**
* @param vertex_capacity
* @uml.property name="vertex_capacity"
*/
public void setVertex_capacity(int vertex_capacity) {
this.vertex_capacity = vertex_capacity;
}
/**
* @return
* @uml.property name="unique_vertex_count"
*/
public int getUnique_vertex_count() {
return unique_vertex_count;
}
/**
* @param unique_vertex_count
* @uml.property name="unique_vertex_count"
*/
public void setUnique_vertex_count(int unique_vertex_count) {
this.unique_vertex_count = unique_vertex_count;
}
/**
* @return
* @uml.property name="vertices"
*/
public ArrayList<Vertex> getVertices() {
return vertices;
}
/**
* @param vertices
* @uml.property name="vertices"
*/
public void setVertices(ArrayList<Vertex> vertices) {
this.vertices = vertices;
}
/**
* @return
* @uml.property name="face_count"
*/
public int getFace_count() {
return face_count;
}
/**
* @param face_count
* @uml.property name="face_count"
*/
public void setFace_count(int face_count) {
this.face_count = face_count;
}
/**
* @return
* @uml.property name="tri_count"
*/
public int getTri_count() {
return tri_count;
}
/**
* @param tri_count
* @uml.property name="tri_count"
*/
public void setTri_count(int tri_count) {
this.tri_count = tri_count;
}
/**
* @return
* @uml.property name="quad_count"
*/
public int getQuad_count() {
return quad_count;
}
/**
* @param quad_count
* @uml.property name="quad_count"
*/
public void setQuad_count(int quad_count) {
this.quad_count = quad_count;
}
/**
* @return
* @uml.property name="poly_count"
*/
public int getPoly_count() {
return poly_count;
}
/**
* @param poly_count
* @uml.property name="poly_count"
*/
public void setPoly_count(int poly_count) {
this.poly_count = poly_count;
}
/**
* @return
* @uml.property name="line_count"
*/
public int getLine_count() {
return line_count;
}
/**
* @param line_count
* @uml.property name="line_count"
*/
public void setLine_count(int line_count) {
this.line_count = line_count;
}
/**
* @return
* @uml.property name="face_capacity"
*/
public int getFace_capacity() {
return face_capacity;
}
/**
* @param face_capacity
* @uml.property name="face_capacity"
*/
public void setFace_capacity(int face_capacity) {
this.face_capacity = face_capacity;
}
/**
* @return
* @uml.property name="faces"
*/
public Face[] getFaces() {
return faces;
}
/**
* @param faces
* @uml.property name="faces"
*/
public void setFaces(Face[] faces) {
this.faces = faces;
}
/**
* @return
* @uml.property name="rtree"
*/
public RTree getRtree() {
return rtree;
}
/**
* @param rtree
* @uml.property name="rtree"
*/
public void setRtree(RTree rtree) {
this.rtree = rtree;
}
/**
* @return
* @uml.property name="highest_tid"
*/
public int getHighest_tid() {
return highest_tid;
}
/**
* @param highest_tid
* @uml.property name="highest_tid"
*/
public void setHighest_tid(int highest_tid) {
this.highest_tid = highest_tid;
}
// Add one face to the mesh. Quads and tris can be added in any order but
// all
// quads and tris (polygons) must be added before all lines.
// When passing a face, simply passnull for any 'extra' vertices - that
// is,
// to create a line, passnull for p3 and p4; to create a triangle, pass
// null for
// p3. The color is the color of the entire face in RGBA; the face normal
// is
// computed for you.
//
// tid is the 'texture ID', a 0-based counted number identifying which
// texture
// state this face gets. Texture IDs must be consecutive, zero based and
// positive, but do not need to be submitted in any particular order, and
// the
// highest TID does not have to be pre-declared; the library simply
// watches
// the TIDs on input.
//
// Technically lines have TIDs as well - typically TID 0 is used to mean
// the
// 'untextured texture group' and is used for lines and untextured
// polygons.
//
// The TIDs are used to output sets of draw commands that share common
// texture state -
// that is, faces, quads and lines are ouput in TID order.
public void add_face(float p1[], float p2[], float p3[], float p4[],
float color[], int tid) {
int i;
// grab a new face, grab verts for it
Face f = faces[face_count++];
f.setTid(tid);
if (tid > highest_tid)
highest_tid = tid;
if (p3 != null) {
float v1[] = { p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2] };
float v2[] = { p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2] };
MeshSmooth.vec3_cross(f.getNormal(), v1, v2);
MeshSmooth.vec3f_normalize(f.getNormal());
} else {
float normal[] = f.getNormal();
normal[0] = normal[2] = 0.0f;
normal[1] = 1.0f;
}
f.setDegree(p4 != null ? 4 : (p3 != null ? 3 : 2));
f.setVertexAtIndex(0, vertices.get(vertex_count++));
f.setVertexAtIndex(1, vertices.get(vertex_count++));
if (p3 != null)
f.setVertexAtIndex(2, vertices.get(vertex_count++));
else
f.setVertexAtIndex(2, null);
if (p4 != null)
f.setVertexAtIndex(3, vertices.get(vertex_count++));
else
f.setVertexAtIndex(3, null);
Vertex[] vertex = f.getVertex();
Face[] neighbor = f.getNeighbor();
neighbor[0] = neighbor[1] = neighbor[2] = neighbor[3] = null;
f.setNeighbor(neighbor);
VertexInsert[] t_list = f.getT_list();
t_list[0] = t_list[1] = t_list[2] = t_list[3] = null;
f.setT_list(t_list);
int[] index = f.getIndex();
index[0] = index[1] = index[2] = index[3] = -1;
f.setIndex(index);
int[] flip = f.getFlip();
flip[0] = flip[1] = flip[2] = flip[3] = -1;
f.setFlip(flip);
MeshSmooth.vec4f_copy(f.getColor(), color);
for (i = 0; i < f.getDegree(); ++i) {
MeshSmooth.vec3f_copy(vertex[i].getNormal(), f.getNormal());
MeshSmooth.vec4f_copy(vertex[i].getColor(), color);
vertex[i].setPrev(null);
vertex[i].setNext(null);
}
MeshSmooth.vec3f_copy(vertex[0].getLocation(), p1);
MeshSmooth.vec3f_copy(vertex[1].getLocation(), p2);
if (p3 != null)
MeshSmooth.vec3f_copy(vertex[2].getLocation(), p3);
if (p4 != null)
MeshSmooth.vec3f_copy(vertex[3].getLocation(), p4);
vertex[0].setIndex(0);
vertex[1].setIndex(1);
if (vertex[2] != null)
vertex[2].setIndex(2);
if (vertex[3] != null)
vertex[3].setIndex(3);
vertex[0].setFace(f);
vertex[1].setFace(f);
if (vertex[2] != null)
vertex[2].setFace(f);
if (vertex[3] != null)
vertex[3].setFace(f);
}
// public static void vec3_cross(float[] dst, float[] v1, float[] v2) {
// dst[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
// dst[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
// dst[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
// }
// public static void vec3f_normalize(float N[]) {
// float len = (float) Math.sqrt(N[0] * N[0] + N[1] * N[1] + N[2] * N[2]);
// if (len != 0) {
// len = 1.0f / len;
// N[0] *= len;
// N[1] *= len;
// N[2] *= len;
// }
// }
// copy vec4: d = s
// public static void vec4f_copy(float[] d, float[] s) {
// d[0] = s[0];
// d[1] = s[1];
// d[2] = s[2];
// d[3] = s[3];
// }
// copy vec3: d = s.
// public static void vec3f_copy(float[] d, float[] s) {
// d[0] = s[0];
// d[1] = s[1];
// d[2] = s[2];
// }
// This function does a bunch of post-geometry-adding processing:
// 1. It sorts the vertices in XYZ order for correct indexing. This
// forces colocated vertices together in the list.
// 2. It indexes vertices into an R-tree.
// 3. It performs a two-step snapping process by
// 3a. Locating rings of too-close vertices and
// 3b. Setting each member of the ring to the ring's centroid location.
// 4. Vertices are resorted AGAIN.
// 5. The links from faces to vertices must be rebuilt due to sorting.
// 6. Degenerate quads/tris are marked as 'creased' on all sides.
//
// Notes:
// 2 and 4 are BOTH necessary - the first sort is needed to pre-sorted
// the data for the R-tree interface.
// The second sort is needed because the order of sort is ruined by
// changing XYZ geometry locations.
//
// Re: 6, we don't want to delete degenerate quads (a degen quad
// might be a visible triangle) but passing degenerate geometry to the
// smoother causes problems - so instead we 'seal off' this geometry to
// avoid further problems.
void finish_faces_and_sort() {
int v, f;
// int total_before = 0, total_after = 0;
// sort vertices by 10 params
MeshSmooth.sort_vertices_3(vertices, vertex_count);
// rtree = new RTree(vertices, vertex_count);
// for (v = 0; v < vertex_count; ++v) {
// if (v == 0
// || MeshSmooth.compare_points(vertices.get(v - 1).getLocation(),
// 0, vertices.get(v).getLocation(), 0) != 0) {
// ++total_before;
// Vertex vi = vertices.get(v);
// float mib[] = { vi.getLocation()[0] - MeshSmooth.EPSI,
// vi.getLocation()[1] - MeshSmooth.EPSI,
// vi.getLocation()[2] - MeshSmooth.EPSI };
// float mab[] = { vi.getLocation()[0] + MeshSmooth.EPSI,
// vi.getLocation()[1] + MeshSmooth.EPSI,
// vi.getLocation()[2] + MeshSmooth.EPSI };
// // todo
// //rtree.scan_rtree(index, mib, mab, visit_vertex_to_snap, vi);
// }
// }
for (v = 0; v < vertex_count; ++v)
if (v == 0
|| MeshSmooth
.compare_points(vertices.get(v - 1).getLocation(),
0, vertices.get(v).getLocation(), 0) != 0)
if (vertices.get(v).prev == null) {
if (vertices.get(v).next != null) {
Vertex i;
float count = 0.0f;
float p[] = new float[3];
;
for (i = vertices.get(v); i != null; i = i.getNext()) {
count += 1.0f;
p[0] += i.getLocation()[0];
p[1] += i.getLocation()[1];
p[2] += i.getLocation()[2];
}
assert (count > 0.0f);
count = 1.0f / count;
p[0] *= count;
p[1] *= count;
p[2] *= count;
i = vertices.get(v);
while (i != null) {
boolean has_more = false;
Vertex k = i;
i = i.getNext();
do {
has_more = 1 < vertex_count
&& MeshSmooth.compare_points(k
.getLocation(), 0, k.getNext()
.getLocation(), 0) == 0;
k.getLocation()[0] = p[0];
k.getLocation()[1] = p[1];
k.getLocation()[2] = p[2];
k = k.getNext();
k.getPrev().setPrev(null);
k.getPrev().setNext(null);
} while (has_more);
}
}
// ++total_after;
}
// printf("BEFORE: %d, AFTER: %d\n", total_before, total_after);
MeshSmooth.sort_vertices_3(vertices, vertex_count);
// then re-build ptr indices into faces since we moved vertices
for (v = 0; v < vertex_count; ++v) {
vertices.get(v).face.setVertexAtIndex(vertices.get(v).getIndex(),
vertices.get(v));
}
for (f = 0; f < face_count; ++f) {
if (faces[f].degree == 3) {
float[] p1 = faces[f].vertex[0].getLocation();
float[] p2 = faces[f].vertex[1].getLocation();
float[] p3 = faces[f].vertex[2].getLocation();
if (MeshSmooth.compare_points(p1, 0, p2, 0) == 0
|| MeshSmooth.compare_points(p2, 0, p3, 0) == 0
|| MeshSmooth.compare_points(p1, 0, p3, 0) == 0) {
faces[f].neighbor[0] = faces[f].neighbor[1] = faces[f].neighbor[2] = faces[f].neighbor[3] = null;
}
}
if (faces[f].degree == 4) {
float[] p1 = faces[f].vertex[0].getLocation();
float[] p2 = faces[f].vertex[1].getLocation();
float[] p3 = faces[f].vertex[2].getLocation();
float[] p4 = faces[f].vertex[3].getLocation();
if (MeshSmooth.compare_points(p1, 0, p2, 0) == 0
|| MeshSmooth.compare_points(p2, 0, p3, 0) == 0
|| MeshSmooth.compare_points(p1, 0, p3, 0) == 0
|| MeshSmooth.compare_points(p3, 0, p4, 0) == 0
|| MeshSmooth.compare_points(p2, 0, p4, 0) == 0
|| MeshSmooth.compare_points(p1, 0, p4, 0) == 0) {
faces[f].neighbor[0] = faces[f].neighbor[1] = faces[f].neighbor[2] = faces[f].neighbor[3] = null;
}
}
}
}
// This marks every line added to the mesh as a crease. This
// ensures we won't smooth across our type 2 lines.
void add_creases() {
int fi;
Face f;
for (fi = poly_count; fi < face_count; ++fi) {
f = faces[fi];
assert (f.getDegree() == 2);
Vertex[] vertex = f.getVertex();
add_crease(vertex[0].getLocation(), vertex[1].getLocation());
}
}
// Utility function: this marks one edge as a crease - the edge is
// identified by its
// location.
public void add_crease(float p1[], float p2[]) {
int begin, end;
Vertex v;
float pp1[] = { p1[0], p1[1], p1[2] };
float pp2[] = { p2[0], p2[1], p2[2] };
Range resultRange = MeshSmooth.range_for_point(vertices, 0,
vertex_count, pp1);
begin = resultRange.getLocation();
end = resultRange.getMaxRange();
for (int counter = begin; counter <= end; counter++) {
v = vertices.get(counter);
Face f = v.face;
// CCW The index of neighbor "A" is index; the index of neighbor b
// is
// CCW.
// / \ The index of neighbor C is CW.
// b a So...if CW=p2 we found c;
// / \ if CCW=p2 we found a.
// CW---c---INDEX
//
//
// // //
int ccw = MeshSmooth.CCW(f, v.getIndex());
int cw = MeshSmooth.CW(f, v.getIndex());
Vertex[] vertex = f.getVertex();
Face[] neighbor = f.getNeighbor();
int[] index = f.getIndex();
if (MeshSmooth.compare_points(vertex[cw].getLocation(), 0, pp2, 0) == 0) {
// We found "C" - nuke at 'cw'
neighbor[cw] = null;
index[cw] = -1;
}
if (MeshSmooth.compare_points(vertex[ccw].getLocation(), 0, pp2, 0) == 0) {
// We fond "A" - nuke at 'index'
neighbor[v.getIndex()] = null;
index[v.getIndex()] = -1;
}
}
}
// #pragma mark -
// ==============================================================================
// TRIANGLE MESH UTILS
// ==============================================================================
// This routine finds and removes all T junctions from the mesh. It does
// this by...
// For each non-creased edge (we don't de-T creases for speed) we R-tree
// search for
// all T-forming vertices and put them in a sorted linked list by edge.
//
// Then we build a brand new copy of our mesh and peel off ears for each
// interference
// as new triangles. When done, we're left with triangles that we also add.
//
// Finall we redo a bunch of processing we already did, now that we have a
// new mesh.
void find_and_remove_t_junctions() {
assert (vertex_count == vertex_capacity);
assert (face_count == face_capacity);
t_finder_info_t info = new t_finder_info_t();
int fi;
info.inserted_pts = 0;
info.split_quads = 0;
for (fi = 0; fi < poly_count; ++fi) {
info.f = faces[fi];
if (info.f.degree > 2)
for (info.i = 0; info.i < info.f.degree; ++info.i) {
// Sad -- this is not a win - this info is not yet
// available. :-(
if (info.f.neighbor[info.i] == null)
continue;
info.v1 = info.f.vertex[info.i];
info.v2 = info.f.vertex[(info.i + 1) % info.f.degree];
if (MeshSmooth.vec3f_eq(info.v1.getLocation(),
info.v2.getLocation()))
continue;
info.line_dir[0] = info.v2.getLocation()[0]
- info.v1.getLocation()[0];
info.line_dir[1] = info.v2.getLocation()[1]
- info.v1.getLocation()[1];
info.line_dir[2] = info.v2.getLocation()[2]
- info.v1.getLocation()[2];
// vec3f_normalize(info.line_dir);
float mib[] = {
Math.min(info.v1.getLocation()[0],
info.v2.getLocation()[0])
- MeshSmooth.EPSI,
Math.min(info.v1.getLocation()[1],
info.v2.getLocation()[1])
- MeshSmooth.EPSI,
Math.min(info.v1.getLocation()[2],
info.v2.getLocation()[2])
- MeshSmooth.EPSI };
float mab[] = {
Math.max(info.v1.getLocation()[0],
info.v2.getLocation()[0])
+ MeshSmooth.EPSI,
Math.max(info.v1.getLocation()[1],
info.v2.getLocation()[1])
+ MeshSmooth.EPSI,
Math.max(info.v1.getLocation()[2],
info.v2.getLocation()[2])
+ MeshSmooth.EPSI };
// todo
// rtree.scan_rtree(mib, mab, visit_possible_t_junc, &info);
}
}
// printf("Subdivided %d quads and added %d pts.\n",
// info.split_quads,info.inserted_pts);
if (info.inserted_pts > 0) {
int f;
Mesh new_mesh;
assert (info.split_quads <= quad_count);
new_mesh = new Mesh(tri_count + info.inserted_pts + 2
* info.split_quads, quad_count - info.split_quads,
line_count);
for (f = 0; f < face_count; ++f) {
Face fp = faces[f];
if (fp.t_list[0] == null && fp.t_list[1] == null
&& fp.t_list[2] == null && fp.t_list[3] == null) {
switch (fp.degree) {
case 2:
new_mesh.add_face(fp.vertex[0].getLocation(),
fp.vertex[1].getLocation(), null, null,
fp.color, fp.tid);
break;
case 3:
new_mesh.add_face(fp.vertex[0].getLocation(),
fp.vertex[1].getLocation(),
fp.vertex[2].getLocation(), null, fp.color,
fp.tid);
break;
case 4:
new_mesh.add_face(fp.vertex[0].getLocation(),
fp.vertex[1].getLocation(),
fp.vertex[2].getLocation(),
fp.vertex[3].getLocation(), fp.color, fp.tid);
break;
default:
assert true : "bad degree.";
}
} else {
int i;
int total_pts = 0;
VertexInsert vp;
float[] poly;
int write_cnt = 0;
for (i = 0; i < fp.degree; ++i) {
++total_pts;
for (vp = fp.t_list[i]; vp != null; vp = vp.next)
++total_pts;
}
poly = new float[3 * total_pts];
write_cnt = 0;
for (i = 0; i < fp.degree; ++i) {
for (int j = 0; j < 3; j++)
poly[write_cnt + j] = fp.vertex[i].getLocation()[j];
write_cnt += 3;
for (vp = fp.t_list[i]; vp != null; vp = vp.next) {
for (int j = 0; j < 3; j++)
poly[write_cnt + j] = vp.vert.getLocation()[j];
write_cnt += 3;
}
}
while (total_pts > 3) {
add_ear_and_remove(poly, total_pts, new_mesh, fp.color,
fp.tid);
--total_pts;
}
float[] p1, p2, p3;
p1 = new float[3];
p2 = new float[3];
p3 = new float[3];
FloatBuffer tempFloatBuffer = FloatBuffer.wrap(poly);
tempFloatBuffer.get(p1, 3, 3);
tempFloatBuffer.get(p2, 6, 3);
new_mesh.add_face(poly, p1, p2, null, fp.color, fp.tid);
}
}
// assert(new_vertex_count == new_vertex_capacity);
// assert(new_face_count == new_face_capacity);
new_mesh.finish_faces_and_sort();
new_mesh.add_creases();
Mesh temp;
Mesh.swap(new_mesh, this);
// memcpy(&temp,mesh,sizeof(Mesh));
// memcpy(mesh,new_mesh,sizeof(Mesh));
// memcpy(new_mesh,&temp,sizeof(Mesh));
// destroy_mesh(new_mesh);
}
}
public void setMesh(Mesh newMesh) {
setVertex_count(newMesh.getVertex_count());
setVertex_capacity(newMesh.getVertex_capacity());
setUnique_vertex_count(newMesh.getUnique_vertex_count());
setVertices(newMesh.getVertices());
setFace_capacity(newMesh.getFace_capacity());
setFace_count(newMesh.getFace_count());
setFaces(newMesh.getFaces());
setTri_count(newMesh.getTri_count());
setQuad_count(newMesh.getQuad_count());
setPoly_count(newMesh.getPoly_count());
setLine_count(newMesh.getLine_count());
setRtree(newMesh.getRtree());
setHighest_tid(newMesh.getHighest_tid());
}
// (int tri_count, int quad_count, int line_count) {
private static void swap(Mesh new_mesh, Mesh mesh) {
Mesh temp = new Mesh(0, 0, 0);
temp.setMesh(new_mesh);
new_mesh.setMesh(mesh);
;
mesh.setMesh(temp);
}
void add_ear_and_remove(float[] poly, int pt_count, Mesh target_mesh,
float[] color, int tid) {
int i, p, n, b = -1;
float best_dot = -99.0f;
for (i = 0; i < pt_count; ++i) {
float[] p1, p2, p3;
float v1[], v2[];
v1 = new float[3];
v2 = new float[3];
float dot;
p = (i + pt_count - 1) % pt_count;
n = (i + 1) % pt_count;
p1 = new float[3];
p2 = new float[3];
p3 = new float[3];
FloatBuffer tempFloatBuffer = FloatBuffer.wrap(poly);
tempFloatBuffer.get(p1, 3 * p, 3);
tempFloatBuffer.get(p2, 3 * i, 3);
tempFloatBuffer.get(p3, 3 * n, 3);
MeshSmooth.vec3f_diff(v1, p2, p1);
MeshSmooth.vec3f_diff(v2, p2, p3);
MeshSmooth.vec3f_normalize(v1);
MeshSmooth.vec3f_normalize(v2);
dot = MeshSmooth.vec3f_dot(v1, v2);
if (dot > best_dot || b == -1) {
best_dot = dot;
b = i;
}
}
assert (b >= 0);
assert (b < pt_count);
p = (b + pt_count - 1) % pt_count;
n = (b + 1) % pt_count;
float[] p1, p2, p3;
p1 = new float[3];
p2 = new float[3];
p3 = new float[3];
FloatBuffer tempFloatBuffer = FloatBuffer.wrap(poly);
tempFloatBuffer.get(p1, 3 * p, 3);
tempFloatBuffer.get(p2, 3 * b, 3);
tempFloatBuffer.get(p3, 3 * n, 3);
target_mesh.add_face(p1, p2, p3, null, color, tid);
if (b != pt_count - 1) {
for (int j = 0; j < (pt_count - b - 1) * 3; j++)
poly[3 * b + j] = poly[3 * b + 3 + j];
}
}
// Once all creases have been marked, this routine locates all colocated
// mesh
// edges going in opposite directions (opposite direction colocated edges
// mean
// the faces go in the same direction) that are not already marked as
// neighbors
// or creases. If the potential join between faces is too sharp, it is
// marked
// as a crease, otherwise the edges are recorded as neighbors of each other.
// When we are done every polygon edge is a crease or neighbor of someone.
void finish_creases_and_join() {
int fi;
int i;
Face f;
for (fi = 0; fi < poly_count; ++fi) {
f = faces[fi];
assert (f.degree >= 3);
for (i = 0; i < f.degree; ++i) {
if (f.neighbor[i] == null) {
// CCW(i)/P1
// / \ The directed edge we want goes FROM i TO ccw.
// / i So p2 = ccw, p1 = i, that is, we want our OTHER
// / \ neighbor to go FROM cw TO CCW
// .---------i/P2
int p1Index = MeshSmooth.CCW(f, i);
Vertex p1 = f.vertex[p1Index];
Vertex p2 = f.vertex[i];
int begin, end, v;
// Range result = MeshSmooth.range_for_point(vertices[0],
// vertices[vertex_count-1], vertex_count,
// p1.getLocation());
Range result = MeshSmooth.range_for_vertex(vertices, 0,
vertex_count, p1Index);
begin = result.getLocation();
end = result.getMaxRange();
for (v = begin; v <= end; v++) {
if (vertices.get(v).face == f)
continue;
// P1/v-----x Normal case - Since p1.p2 is the ideal
// direction of our
// \ / neighbor, p2 = ccw(v). Thus p1(v) names our edge.
// v /
// \ /
// P2/CCW(V)
// P1/v-----x Backward winding case - thus p2 is CW from
// P1,
// \ / and P2 (cw(v) names our edge.
// cw(v) /
// \ /
// P2/CW(V)
assert (MeshSmooth.compare_points(p1.getLocation(), 0,
vertices.get(v).getLocation(), 0) == 0);
Face n = vertices.get(v).face;
Vertex dst = n.vertex[MeshSmooth.CCW(n, vertices.get(v)
.getIndex())];
Vertex inv = n.vertex[MeshSmooth.CW(n, vertices.get(v)
.getIndex())];
if (dst.face.degree > 2)
if (MeshSmooth.compare_points(dst.getLocation(), 0,
p2.getLocation(), 0) == 0) {
int ni = vertices.get(v).getIndex();
assert (f.neighbor[i] == null);
if (n.neighbor[ni] == null) {
if (MeshSmooth.is_crease(f.normal,
n.normal, false)) {
f.neighbor[i] = null;
n.neighbor[ni] = null;
f.index[i] = -1;
n.index[ni] = -1;
break;
} else
{
// v.dst matches p1.p2. We have
// neighbors.
// Store both - avoid half the work when
// we get to our neighbor.
f.neighbor[i] = n;
n.neighbor[ni] = f;
f.index[i] = ni;
n.index[ni] = i;
f.flip[i] = 0;
n.flip[ni] = 0;
break;
}
}
}
if (inv.face.degree > 2)
if (MeshSmooth.compare_points(inv.getLocation(), 0,
p2.getLocation(), 0) == 0) {
int ni = MeshSmooth.CW(vertices.get(v).face,
vertices.get(v).getIndex());
assert (f.neighbor[i] == null);
if (n.neighbor[ni] == null) {
if (MeshSmooth.is_crease(f.normal,
n.normal, true)) {
f.neighbor[i] = null;
n.neighbor[ni] = null;
f.index[i] = -1;
n.index[ni] = -1;
break;
} else
{
// v.dst matches p1.p2. We have
// neighbors.
// Store both - avoid half the work when
// we get to our neighbor.
f.neighbor[i] = n;
n.neighbor[ni] = f;
f.index[i] = ni;
n.index[ni] = i;
f.flip[i] = 1;
n.flip[ni] = 1;
break;
}
}
}
}
}
if (f.neighbor[i] == null) {
f.neighbor[i] = null;
f.index[i] = -1;
}
}
}
}
// Once all neighbors have been found, this routine calculates the
// actual per-vertex smooth normals. This is done by circulating
// each vertex (via its neighbors) to find all contributing triangles,
// computing a weighted average (from for each triangle) and applying
// the new averaged normal to all participating vertices.
//
// A few key points:
// - Circulation around the vertex only goes by neigbhor. So creases
// (lack of a neighbor) partition the triangles around our vertex into
// adjacent groups, each of which get their own smoothing.
// - This is what makes a 'creased' shape flat-shaded: the creases keep
// us from circulating more than one triangle.
// - We weight our average normal by the angle the triangle spans around
// the vertex, not just a straight average of all participating triangles.
// We do not want to bias our normal toward the direction of more small
// triangles.
void smooth_vertices() {
int f;
int i;
for (f = 0; f < poly_count; ++f)
for (i = 0; i < faces[f].degree; ++i) {
// For each vertex, we are going to circulate around attached
// faces, averaging up our normals.
Vertex v = faces[f].vertex[i];
// First, go clock-wise around, starting at ourselves, until we
// loop back on ourselves (a closed smooth
// circuite - the center vert on a stud top is like this) or we
// run out of vertices.
Vertex c = v;
float N[] = new float[3];
IntBuffer circ_dir = IntBuffer.allocate(1);
circ_dir.put(-1);
float w;
do {
// printf("\tAdd: %f,%f,%f\n",c.normal[0],c.normal[1],c.normal[2]);
w = MeshSmooth.weight_for_vertex(c);
if (MeshSmooth.vec3f_dot(v.face.normal, c.face.normal) > 0.0f) {
N[0] += w * c.face.normal[0];
N[1] += w * c.face.normal[1];
N[2] += w * c.face.normal[2];
} else {
N[0] -= w * c.face.normal[0];
N[1] -= w * c.face.normal[1];
N[2] -= w * c.face.normal[2];
}
c = MeshSmooth.circulate_any(c, circ_dir);
} while (c != null && c != v);
// Now if we did NOT make it back to ourselves it means we are a
// disconnected circulation. For example
// a semi-circle fan's center will do this if we start from a
// middle tri.
// Circulate in the OTHER direction, skipping ourselves, until
// we run out.
if (c != v) {
circ_dir.put(0, 1);
c = MeshSmooth.circulate_any(v, circ_dir);
while (c != null) {
// printf("\tAdd: %f,%f,%f\n",c.normal[0],c.normal[1],c.normal[2]);
w = MeshSmooth.weight_for_vertex(c);
if (MeshSmooth.vec3f_dot(v.face.normal, c.face.normal) > 0.0) {
N[0] += w * c.face.normal[0];
N[1] += w * c.face.normal[1];
N[2] += w * c.face.normal[2];
} else {
N[0] -= w * c.face.normal[0];
N[1] -= w * c.face.normal[1];
N[2] -= w * c.face.normal[2];
}
c = MeshSmooth.circulate_any(c, circ_dir);
// Invariant: if we did NOT close-loop up top, we should
// NOT close-loop down here - that would imply
// a triangulation where our neighbor info was
// assymetric, which would be "bad".
assert (c != v);
}
}
MeshSmooth.vec3f_normalize(N);
// printf("Final: %f %f %f\t%f %f %f (%d)\n",v.getLocation()[0],v.getLocation()[1],v.getLocation()[2],
// N[0],N[1],N[2], ctr);
v.normal[0] = N[0];
v.normal[1] = N[1];
v.normal[2] = N[2];
}
}
// This routine merges vertices that have the same complete (10-float)
// value to optimize down the size of geometry in VRAM. We do this
// by resorting by all components and then recognizing adjacently equal
// vertices. This routine rebuilds the ptrs from triangles so that all
// faces see the 'shared' vertex. By convention the first of a group
// of equal vertices in the vertex array is the one we will keep/use.
void merge_vertices() {
// Once smoothing is done, indexing the mesh is actually pretty easy:
// First, we re-sort the vertex list; now that our normals and colors
// are consolidated, equal-value vertices in the final mesh will pool
// together.
//
// Then (having moved our vertices) we need to rebuild the face.vertex
// pointers...when we do this, we simply use the FIRST vertex among
// equals for every face.
//
// The result is that for every redundent vertex, every face will
// agree on which ptr to use. This means that we can build an indexed
// mesh off of the ptrs and get maximum sharing.
int v;
int unique = 0;
Vertex first_of_equals = vertices.get(0);
// Resort according ot our xyz + normal + color
MeshSmooth.sort_vertices_10(vertices, vertex_count);
// Re-set the tri ptrs again, but...for each IDENTICAL source vertex,
// use the FIRST of them as the ptr
for (v = 0; v < vertex_count; ++v) {
if (MeshSmooth.compare_vertices(first_of_equals, vertices.get(v)) != 0) {
first_of_equals = vertices.get(v);
}
vertices.get(v).face.vertex[vertices.get(v).index] = first_of_equals;
if (vertices.get(v) == first_of_equals) {
vertices.get(v).index = -1;
++unique;
} else {
vertices.get(v).index = -2;
}
}
unique_vertex_count = unique;
// System.out.println("Before: "+vertex_count+ ", after: "+unique);
}
// This returns the final counts for vertices and indices in a mesh - after
// merging,
// subdivising, etc. our original counts may be changed, so clients need to
// know
// how much VBO space to allocate.
void get_final_mesh_counts(IntBuffer total_vertices, IntBuffer total_indices) {
total_vertices.put(0, unique_vertex_count);
total_indices.put(0, vertex_count);
}
// This cleans our mesh, deallocating all internal memory.
void destroy_mesh() {
// nothing to do. everything will be done by GC.
}
// This routine writes out the final smoothed mesh. It takes:
// - Buffer space for the vertex table (10x floats per vertex)
// - Buffer space for the indices (1 uint per index)
// - Pointers to variable-sized arrays to take the start/count for
// each kind of primitive for each TID.
// In other words, out_line_starts[0] contains the offset into our
// index buffer of the lines for TID 0. out_quad_counts[2] contains
// the number of indices for all quads in TID 2.
// max(tids)+1 ints should be allocated for each output array.
//
// The indices are written in TID order, so that at most three draw calls
// (one each for tris, lines and quads) can be used to draw each TID's
// collection of geometry. Primitives are also output in order.
//
// (In other words, the primary sort key is TID, second is primitive
// type.)
void write_indexed_mesh(int vertex_table_size, FloatBuffer io_vertex_table,
int index_table_size, IntBuffer io_index_table, int index_base,
IntBuffer out_line_starts, IntBuffer out_line_counts,
IntBuffer out_tri_starts, IntBuffer out_tri_counts,
IntBuffer out_quad_starts, IntBuffer out_quad_counts) {
IntBuffer starts[] = { null, null, out_line_starts, out_tri_starts,
out_quad_starts };
IntBuffer counts[] = { null, null, out_line_counts, out_tri_counts,
out_quad_counts };
FloatBuffer vert_ptr = io_vertex_table;
int vertCount = 0;
IntBuffer index_ptr = io_index_table;
int indexCount = 0;
int cur_idx = index_base;
int d, i, vi, ti;
Vertex v, vv;
Face f;
// Outer loop: we are going to make one pass over the vertex array
// for each depth of primitive - in other words, we are going to
// 'fish out' all lines first, then all tris, then all quads.
for (ti = 0; ti <= highest_tid; ++ti)
for (d = 2; d <= 4; ++d) {
starts[d].put(ti, indexCount);
for (vi = 0; vi < vertex_count; ++vi) {
v = vertices.get(vi);
f = v.face;
// For each vertex, we look at its face if it qualifies.
// This way we write the faces in sorted vertex order.
if (f.degree == d)
if (f.tid == ti) {
for (i = 0; i < d; ++i) {
vv = f.vertex[i];
assert (vv.getIndex() != -2);
// To write out our vertices, we MAY need to
// write out the vertex if it is first used.
// Thus the vertices go down in approximate
// usage
// order, which is good.
if (vv.getIndex() == -1) {
vv.setIndex(cur_idx++);
vert_ptr.put(vertCount++,
vv.getLocation()[0]);
vert_ptr.put(vertCount++,
vv.getLocation()[1]);
vert_ptr.put(vertCount++,
vv.getLocation()[2]);
vert_ptr.put(vertCount++, vv.normal[0]);
vert_ptr.put(vertCount++, vv.normal[1]);
vert_ptr.put(vertCount++, vv.normal[2]);
vert_ptr.put(vertCount++, vv.color[0]);
vert_ptr.put(vertCount++, vv.color[1]);
vert_ptr.put(vertCount++, vv.color[2]);
vert_ptr.put(vertCount++, vv.color[3]);
}
assert (vv.getIndex() >= 0);
index_ptr.put(indexCount++, (vv.getIndex()));
;
}
// when the face is done, we mark it via degree = 0
// so we
// don't hit it again when we hit one of its
// vertices due to
// sharing.
f.setDegree(0);
} // end of face write-out for matched faces
} // end of linear vertex walk
counts[d].put(ti, (indexCount - starts[d].get(ti)));
} // end of primitve sort
// assert(vert_ptr == vert_stop);
// assert(index_ptr == index_stop);
}
public int getTotalVertices() {
return unique_vertex_count;
}
public int getTotalIndices() {
return vertex_count;
}
}
class t_finder_info_t {
/**
* @uml.property name="split_quads"
*/
int split_quads; // The number of quads that have been split. Each quad with
// a
// subdivision must be triangulated, changing our face
// count, so
// we have to track this.
/**
* @uml.property name="inserted_pts"
*/
int inserted_pts; // Number of points inserted into edges - this increases
// triangle
// count so we must track it.
/**
* @uml.property name="v1"
* @uml.associationEnd
*/
Vertex v1; // Start and end vertices of the edge we are testing.
/**
* @uml.property name="v2"
* @uml.associationEnd
*/
Vertex v2;
/**
* @uml.property name="f"
* @uml.associationEnd
*/
Face f; // The face that v1/v2 belong to.
/**
* @uml.property name="i"
*/
int i; // The side index i of face f that we are tracking, e.g.
// f.vertices[i] == v1
/**
* @uml.property name="line_dir"
*/
float line_dir[]; // A normalized direction vector from v1 to v2, used to
// order the intrusions.
/**
* @return
* @uml.property name="split_quads"
*/
public int getSplit_quads() {
return split_quads;
}
/**
* @param split_quads
* @uml.property name="split_quads"
*/
public void setSplit_quads(int split_quads) {
this.split_quads = split_quads;
}
/**
* @return
* @uml.property name="inserted_pts"
*/
public int getInserted_pts() {
return inserted_pts;
}
/**
* @param inserted_pts
* @uml.property name="inserted_pts"
*/
public void setInserted_pts(int inserted_pts) {
this.inserted_pts = inserted_pts;
}
/**
* @return
* @uml.property name="v1"
*/
public Vertex getV1() {
return v1;
}
/**
* @param v1
* @uml.property name="v1"
*/
public void setV1(Vertex v1) {
this.v1 = v1;
}
/**
* @return
* @uml.property name="v2"
*/
public Vertex getV2() {
return v2;
}
/**
* @param v2
* @uml.property name="v2"
*/
public void setV2(Vertex v2) {
this.v2 = v2;
}
/**
* @return
* @uml.property name="f"
*/
public Face getF() {
return f;
}
/**
* @param f
* @uml.property name="f"
*/
public void setF(Face f) {
this.f = f;
}
/**
* @return
* @uml.property name="i"
*/
public int getI() {
return i;
}
/**
* @param i
* @uml.property name="i"
*/
public void setI(int i) {
this.i = i;
}
/**
* @return
* @uml.property name="line_dir"
*/
public float[] getLine_dir() {
return line_dir;
}
/**
* @param line_dir
* @uml.property name="line_dir"
*/
public void setLine_dir(float[] line_dir) {
this.line_dir = line_dir;
}
};